package dao

import (
	"context"
	"gorm.io/gorm"
	"time"
)

// Job 数据库持久对象PO
type Job struct {
	Id       int64 `gorm:"primaryKey,autoIncrement"`
	Cfg      string
	Name     string
	Executor string
	Expr     string // cron 表达式
	Status   int    `gorm:"index"` // 任务状态
	NextTime int64  `gorm:"index"` // 下一次可以被调度的时间
	Ctime    int64
	Utime    int64
}

const (
	JobStatusWaiting = iota
	JobStatusRunning
	JobStatusCompleted
	JobStatusCanceled
)

type JobDAO interface {
	Preempt(ctx context.Context) (Job, error)
	UpdateUtime(ctx context.Context, id int64) error
	Release(ctx context.Context, id int64) error
	UpdateNextTime(ctx context.Context, id int64, time int64) error
}

type GORMJobDAO struct {
	db *gorm.DB
}

func NewGORMJobDAO(db *gorm.DB) JobDAO {
	return &GORMJobDAO{
		db: db,
	}
}

func (dao *GORMJobDAO) UpdateNextTime(ctx context.Context, id int64, t int64) error {
	return dao.db.WithContext(ctx).
		Model(&Job{}).
		Where("id=?", id).
		Updates(map[string]any{
			"utime":     time.Now().UnixMilli(),
			"next_time": t,
		}).Error
}

func (dao *GORMJobDAO) Release(ctx context.Context, id int64) error {
	// 这里也需要加上version机制
	return dao.db.WithContext(ctx).
		Model(&Job{}).
		Where("id=?", id).
		Updates(map[string]any{
			"status": JobStatusWaiting,
			"utime":  time.Now().UnixMilli(),
		}).Error
}

func (dao *GORMJobDAO) UpdateUtime(ctx context.Context, id int64) error {
	return dao.db.WithContext(ctx).
		Model(&Job{}).
		Where("id=?", id).
		Updates(map[string]any{
			"utime": time.Now().UnixMilli(),
		}).Error
}

func (dao *GORMJobDAO) Preempt(ctx context.Context) (Job, error) {
	for {
		// 设计如何抢占
		// 多个goroutine并发执行
		var job Job
		err := dao.db.WithContext(ctx).
			Where("status=? and next_time < ?", JobStatusWaiting, time.Now().UnixMilli()).
			First(&job).Error
		if err != nil {
			// 说明没数据可以抢占
			return Job{}, err
		}
		// 去更新状态（表明该任务已经被抢占了）
		// 这里需要加上status的条件，防止并发安全问题
		res := dao.db.WithContext(ctx).Model(&Job{}).Where("id=? and status=?", job.Id, JobStatusWaiting).Updates(map[string]any{
			"utime":  time.Now().UnixMilli(),
			"status": JobStatusRunning,
		})
		if res.RowsAffected > 0 {
			// 说明抢占成功
			return job, nil
		}
		// 没有抢占成功继续抢占
	}
}
